#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cassert>

using namespace std;

using Byte = unsigned char;
using Word = unsigned short;
using u32 = unsigned int;

struct Mem {
  static constexpr u32 MAX_MEM = 1024 * 64; // 寻址范围16bit
  Byte Data[MAX_MEM];

  void Initialize() {
    for (int i=0; i<MAX_MEM; i++) {
      Data[i] = 0;
    }
  }

  Byte& operator[](int i) {
    return Data[i];
  };

  const Byte& operator[](int i) const { // 利用non-const版本
    return const_cast<Mem&>(*this)[i];
  }
};

bool byte_bit_set(Byte data, int pos) { // is pos bit set ?
  assert(0 <= pos && pos <= 7);
  return data & (1 << pos);
}

struct CPU {
  Word PC; // program counter 16bit
  Byte SP; // stack pointer 8bit

  Byte A, X, Y; // registers

  // process status
  Byte C : 1; // carry flag
  Byte Z : 1; // zero flag
  Byte I : 1; // interrupt disable
  Byte D : 1; // decimal mode
  Byte B : 1; // break command
  Byte V : 1; // overflow flag
  Byte N : 1; // negative flag

  void Reset(Mem &mem) { // 根据具体手册去写，不过手册在哪里？
    // 不一定完全按照手册，能跑起来就好
    PC = 0xFFFC; // 注意0xFFFC在内存边缘(65532 vs 65536) 当时多写了几条指令就超出内存范围了
    SP = 0x0100;
    C = Z = I = D = B = V = N = 0;
    A = X = Y = 0;

    mem.Initialize();
  }

  Byte FetchByte(u32 &cycles, Mem &mem) { // 获取pc指向的内存地址的1byte指令
    Byte data = mem[PC];
    PC++;
    cycles--; // 使用1cycle获取指令
    return data;
  }

  Word FetchWord(u32 &cycles, Mem &mem) { // 返回pc指向位置起2bytes形成的16bit地址
    Word data = *(Word *)&mem[PC];
    PC += 2;
    cycles -= 1;
    return data;
  }

  Byte ReadByte(u32 &cycles, Mem &mem, Byte addr) { // 从内存addr地址(1B范围，zero page范围内)读取1Byte数据
    cycles--;
    Byte value = mem[addr];
    return value;
  }


  /// LDA - Load Accumulator - loads a byte of memory into the accumulator setting the zero (Z) and the negative (N) flags as appropriate
  static constexpr Byte INS_LDA_IM = 0xA9, // opcode后一个byte是立即数
                        INS_LDA_ZP = 0xA5, // opcode后一个byte是某数的地址(因为地址范围8bit，所以数据在zero page中)
                        INS_LDA_ZPX = 0xB5; // opcode后一个byte代表的地址加上X寄存器作为偏移 (专为数组寻址设计)
  /// JMP - Jump - jump to new address
  static constexpr Byte INS_JMP_ABS = 0x4c; // absolute jump, opcode后两个bytes形成16bit地址(能跳转到内存任何地方)

  void LDASetStatus() {
    Z = (A == 0);
    N = byte_bit_set(A, 7);
  }

  void Execute(u32 cycles, Mem &mem) {
    while (cycles > 0) {
      u32 cycles_rec = cycles;
      Byte Ins = FetchByte(cycles, mem); // fetch the instruction
      switch (Ins) {
        case INS_LDA_IM: { // 2 cycles
          A = FetchByte(cycles, mem); // opcode的下一个Byte就是立即数
          LDASetStatus();
          printf("INS_LDA_IM(%d cycles), A=0x%x, Z=%d, N=%d\n", cycles_rec - cycles, A, Z, N);
        } break;
        case INS_LDA_ZP: { // 3 cycles
          Byte zpAddr = FetchByte(cycles, mem); // opcode的下一个Byte是数据在zero page的地址
          A = ReadByte(cycles, mem, zpAddr); // 读取数据
          LDASetStatus();
          printf("INS_LDA_ZP(%d cycles), A=0x%x, Z=%d, N=%d\n", cycles_rec - cycles, A, Z, N);
        } break;
        case INS_LDA_ZPX: { // r cycles
          Byte zpAddr = FetchByte(cycles, mem);
          zpAddr += X; cycles--; // 读取X也需要1byte
          A = ReadByte(cycles, mem, zpAddr);
          LDASetStatus();
          printf("INS_LDA_ZPX(%d cycles), A=0x%x, Z=%d, N=%d\n", cycles_rec - cycles, A, Z, N);
        } break;

        case INS_JMP_ABS: { // 3 cycles
          Word abs_addr = FetchWord(cycles, mem); // opcode后面2bytes形成16bit地址
          cycles -= 1; // jump 需要1 cycle
          PC = abs_addr;
          printf("INS_JMP_ABS(%d cycles), jump to 0x%x\n", cycles_rec - cycles, abs_addr);
        } break;

        default:
          printf("Instruction not handled %x\n", Ins);
          break;
      }
    }
  }
}; // 这么几个寄存器就能让代码跑起来，真神奇

int main() {
  Mem mem;

  CPU cpu;
  cpu.Reset(mem);

  // 写入汇编程序
  int start = 0xFFFC;
  mem[start + 0] = CPU::INS_JMP_ABS;
  mem[start + 1] = 0x34;
  mem[start + 2] = 0x12; // abs: 0x1234 little endian
  mem[0x1234 + 0] = CPU::INS_LDA_IM; // zero page中地址
  mem[0x1234 + 1] = 0x66; // load 0x66 to A

  cpu.Execute(5, mem);

  return 0;
}